import io from "socket.io-client";
import * as Y from "yjs";
import {
  Awareness,
  encodeAwarenessUpdate,
  applyAwarenessUpdate,
} from "y-protocols/awareness";

export class CustomSocketProvider {
  private socket: any;
  public awareness: Awareness;
  public canWrite: boolean = false; // 默认只读，等待服务端下发权限后再放开

  constructor(serverUrl: string, roomName: string, ydoc: Y.Doc) {
    console.log("正在初始化CustomSocketProvider，服务器地址:", serverUrl);

    // 创建一个 socket 连接，配置连接参数
    this.socket = io(serverUrl, {
      // 传输方式：允许 websocket 与 polling，确保 Engine.IO 在需要时能使用带凭证的 polling
      transports: ["websocket", "polling"],

      // 为 polling 传输开启 withCredentials（类型正确的位置）
      transportOptions: {
        polling: { withCredentials: true }
      },

      // 强制创建新连接：每次都创建全新的连接，不复用现有连接（避免缓存问题）
      forceNew: true,

      // 连接超时时间：20秒内无法建立连接则触发 connect_error 事件
      timeout: 20000,

      // 自动连接：创建 Socket 实例时自动尝试连接服务器
      autoConnect: true,

      // 启用自动重连：连接断开时自动尝试重新连接
      reconnection: true,

      // 重连延迟：第一次重连尝试前等待 1 秒
      reconnectionDelay: 1000,

      // 最大重连次数：最多尝试重连 5 次，超过后触发 reconnect_failed 事件
      reconnectionAttempts: 5,

      // === 心跳相关配置（可选，通常使用服务器默认值） ===

      // 心跳超时时间：应该大于等于服务器的 pingTimeout（60秒）
      // 如果在此时间内未收到服务器心跳，客户端认为连接断开
      // timeout: 65000,  // 比服务器稍长一点

      // 是否升级传输方式：从 polling 升级到 websocket（当前只用websocket所以无关）
      upgrade: true,

      // 记住升级状态：避免重复升级检测
      rememberUpgrade: true
    });

    // 初始化一个 Awareness 实例
    this.awareness = new Awareness(ydoc);

    // === 连接状态事件监听 ===

    // 连接成功事件：当与服务器建立连接时触发
    this.socket.on("connect", () => {
      console.log("WebSocket连接成功！Socket ID:", this.socket.id);
      // 连接成功后立即加入指定房间，用于文档协同
      console.log(roomName, 'roomName');

      if (roomName) {
        this.socket.emit("joinRoom", { roomName });
      } else {
        console.warn("joinRoom 跳过：roomName 为空");
      }
    });


    // 连接错误事件：连接失败时触发（如服务器未启动、网络问题等）
    this.socket.on("connect_error", (error: any) => {
      console.error("WebSocket连接错误:", error);
      console.error("错误详情:", {
        message: error.message,        // 错误消息
        description: error.description, // 错误描述
        context: error.context,        // 错误上下文
        type: error.type              // 错误类型
      });
    });

    // 一般错误事件：连接建立后发生的其他错误
    this.socket.on("error", (error: any) => {
      console.error("WebSocket运行时错误:", error);
    });

    // 连接断开事件：与服务器失去连接时触发
    this.socket.on("disconnect", (reason: any) => {
      console.log("WebSocket连接断开，原因:", reason);
      // 如果是服务器主动断开连接，需要手动重连（因为可能是服务器重启）
      if (reason === "io server disconnect") {
        console.log("服务器主动断开，尝试重新连接...");
        this.socket.connect();
      }
    });

    // === 重连相关事件监听 ===

    // 重连成功事件：自动重连成功时触发
    this.socket.on("reconnect", (attemptNumber: number) => {
      console.log("WebSocket重连成功！重试次数:", attemptNumber);
    });
    this.socket.on("permission-denied", () => {
      console.log('无权修改文档');

    });

    // 重连尝试事件：每次尝试重连时触发
    this.socket.on("reconnect_attempt", (attemptNumber: number) => {
      console.log("WebSocket重连尝试中... 第", attemptNumber, "次");
    });

    // 重连错误事件：单次重连尝试失败时触发
    this.socket.on("reconnect_error", (error: any) => {
      console.error("WebSocket重连尝试失败:", error);
    });

    // 重连完全失败事件：达到最大重连次数后仍然失败时触发
    this.socket.on("reconnect_failed", () => {
      console.error("WebSocket重连完全失败！已达到最大重试次数", this.socket.io.opts.reconnectionAttempts);
      console.error("请检查网络连接和服务器状态");
    });

    // === 心跳相关事件监听（可选，用于调试） ===

    // 监听服务器发送的心跳包：服务器每25秒发送一次
    this.socket.on("ping", () => {
      console.log("收到服务器心跳包 ping，客户端将自动回复 pong");
    });

    // 监听心跳响应：客户端发送心跳后收到服务器的响应
    this.socket.on("pong", (latency: any) => {
      console.log("收到服务器心跳响应 pong，延迟:", latency, "ms");
    });

    // 监听 sync-doc 事件，该事件会在 websocket 服务器推送过来完整的文档数据的时候会触发
    // 那么要做的事情：同步我当前客户端的 yjs 文档状态
    // 应用场景：文档初次同步
    // 这里意味着回头 websocket 服务器可以触发这么一个事件
    this.socket.on("sync-doc", (update: ArrayBuffer) => {
      // console.log('sync-doc');
      Y.applyUpdate(ydoc, new Uint8Array(update));
    });

    // websocket 服务器推送过来文档的增量更新内容的时候触发
    // 这里意味着回头 websocket 服务器可以触发这么一个事件
    this.socket.on("update", (update: ArrayBuffer) => {
      Y.applyUpdate(ydoc, new Uint8Array(update));
    });

    // 接收服务端下发的权限信息
    this.socket.on("permission", ({ permission, canWrite }: { permission: 'read' | 'write' | 'admin'; canWrite: boolean; }) => {
      console.log('permission from server', permission, canWrite);
      this.canWrite = !!canWrite;
    });

    // 只读拦截提示
    this.socket.on("permission-denied", (payload: any) => {
      console.log('写入被拒绝:', payload);
    });

    // 当本地文档有变化的时候，需要将 update 进行一个广播
    ydoc.on("update", (update: Uint8Array) => {
      // 只读状态下，不向服务器发送更新

      if (!this.canWrite) return;
      this.socket.emit("update", update.buffer);
    });

    // 在进行协同的时候，除了需要同步文档的内容，还需要同步一些额外的信息
    // 谁在线？谁在输入？谁的光标在什么位置？
    // 这些信息虽然是临时的，但是也很重要，需要进行同步
    // 当本地用户的光标信息发生变化，会触发 update 事件
    this.awareness.on(
      "update",
      ({
        added,
        updated,
        removed,
      }: {
        added: number[]; // 新增的用户的 id 列表
        updated: number[]; // 已存在的用户状态发生了变化（例如光标位置变了）
        removed: number[]; // 离开了协作编辑器的用户的 id
      }) => {
        // console.log('awareness update');

        // 需要将变化的状态信息打包成二进制数据，方便网络传输
        const update = encodeAwarenessUpdate(this.awareness, [
          ...added,
          ...updated,
          ...removed,
        ]);
        // 发送给websocket服务器
        // 这里同样意味着回头我们在写 websocket 服务器的时候，需要监听这么一个 awareness-update 的事件
        this.socket.emit("awareness-update", update);
      }
    );

    // 同理，websocket 服务器给你推送过来的其他用户的新的光标信息
    // 你这边也需要更新
    this.socket.on("awareness-update", (update: ArrayBuffer) => {
      // console.log('socket awareness-update');
      applyAwarenessUpdate(
        this.awareness,
        new Uint8Array(update),
        this.socket.id
      );
    });
  }

  /** 对外暴露的权限订阅，返回取消订阅函数 */
  public onPermission(handler: (payload: { permission: 'read' | 'write' | 'admin'; canWrite: boolean }) => void) {
    this.socket.on('permission', handler as any);
    return () => this.socket.off('permission', handler as any);
  }

  /** 获取当前是否可写 */
  public getCanWrite() {
    return this.canWrite;
  }

  /**
   * 销毁方法，断开websocket连接的时候调用
   */
  destroy() {
    this.socket.disconnect();
  }
}
